package com.gthncz.filter;

import java.io.BufferedReader;
import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.gthncz.beans.AccountCrediential;
import com.gthncz.common.CommonResult;
import com.gthncz.service.JWTAuthenticationService;

public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter{
	
	private static final Logger log = LoggerFactory.getLogger(JwtLoginFilter.class);
	
	@Autowired
	private JWTAuthenticationService jwtAuthenticationService;

	public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
		super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
		this.setAuthenticationManager(authenticationManager);
	}

	/**
	 * <p>
	 * 拦截到 /login 请求后，从输入流中取出 username 和 password ,
	 * 利用 UsernamePasswordAuthenticationToken 装载。
	 * </p>
	 * <p>
	 * 最后 AuthenticationManager 利用 {@code com.gthncz.config.WebSecurityConfig} 中定义的 authenticationProvider 验证，
	 * 程序中用的是 {@code com.gthncz.service.TokenAuthenticationProvider} 验证。
	 * </p>
	 * <p>
	 * 验证成功则会调用 {@code #successfulAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, Authentication)} 回调，
	 * 里面的 Authentication 已经是 authenticated = true 的，并且具有 TokenAuthenticationProvider 中赋予的 权限/角色。
	 * 
	 * 验证失败会调用 {@code #unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException)} 回调，
	 * 里面的 AuthenticationException 含有验证失败的信息。
	 * </p>
	 */
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException, IOException, ServletException {
		log.warn("> JwtLoginFilter attempAuthentication .");
		
		// 从输入流中获取表单数据
		StringBuilder builder = new StringBuilder();
		BufferedReader reader = request.getReader();
		try {
			String line = "";
			while((line = reader.readLine()) != null) builder.append(line);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			reader.close();
		}
		String content = builder.toString();
		
		log.info("JwtLoginFilter get content: {}", content);
		
		// 从表单数据获取 AccountCrediential 对象
		
		AccountCrediential accountCrediential = null;
		try { // com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
			accountCrediential = new ObjectMapper().readValue(content, AccountCrediential.class);
		} catch (MismatchedInputException e) {
			// e.printStackTrace();
			response.setContentType("application/json");
			response.setStatus(HttpServletResponse.SC_OK);
			response.getOutputStream().println(new CommonResult(200, "Error", e.getMessage()).toJson());
			return null;
		}
		
		// 返回一个验证令牌
		UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(accountCrediential.getUsername(), accountCrediential.getPassword());
		return getAuthenticationManager().authenticate(authentication); // 实际上是交给了 TokenAuthenticationProvider 验证
	}

	// 验证成功回调
	@Override
	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			Authentication authResult) throws IOException, ServletException {
		log.warn("> JwtLoginFilter successfulAuthentication: {}", authResult);
		
		jwtAuthenticationService.addAuthentication(response, authResult.getName());
	}

	@Override
	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException failed) throws IOException, ServletException {
		log.warn("> JwtLoginFilter unsuccessfulAuthentication: {}", failed.getMessage());
		
		response.setContentType("applicaton/json");
		response.setStatus(HttpServletResponse.SC_OK);
		response.getOutputStream().println(new CommonResult(500, "Internal Server Error !", failed.getMessage()).toJson());
		
		// ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
		failed.printStackTrace();
	}

}
